﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security;
using System.Net;

namespace App2Wpf
{
    public class clientViewerSession
    {
        public const int ThePORT = 8021;
        public ServerInformation ServerInformation = null;
        public clientViewerConfiguration ViewerConfiguration = null;

        public string ServerAddress = "127.0.0.1";
        public IPAddress Server { get; set; }
        public int ServerPort = 8021;

        public String password = "@Pwd@12345678";

        public SecureString SecurePassword { get; set; }

        public bool UseTLSv1_3 = false;

        public int ImageCompressionQuality = 100;
        public int ResizeRatio = 85;    // set display size
        public PacketSize PacketSize = PacketSize.Size9216;
        public BlockSize BlockSize = BlockSize.Size128;
        public bool LogonUI = false;

        public ClientIO ClientDesktop = null;
        public ClientIO ClientEvents = null;

        public clientViewerSession()
        {
            if (ServerPort < 0 && ServerPort > 65535)
            {
                clientFunc.Showlog("Invalid TCP Port (0-65535)");
            }

            //this.ServerAddress = ServerAddress;
            //this.ServerPort = ServerPort;
            //this.SecurePassword = SecurePassword;

        }

        public void OpenSession() {
            //clientFunc.Showlog("Request new session with remote server: {this.ServerAddress} : {this.ServerPort}");

            if (this.ServerInformation is null)
            {
                clientFunc.Showlog("Request new session with remote server: {this.ServerAddress} : {this.ServerPort}");
            }

            //clientFunc.Showlog("Establish first contact with remote server...");

            ClientIO client = new ClientIO(Server.ToString(), this.ServerPort, this.UseTLSv1_3);

            try
            {
                client.Connect();
                SecureString m_secureString = new NetworkCredential("", password).SecurePassword;
                SecurePassword = m_secureString;
                client.Authentify(m_secureString);

                client.WriteLine((int)ProtocolCommand.RequestSession);

                this.ServerInformation = client.ReadServerInfoJson() ;

                clientFunc.Showlog("@ServerInformation: {this.ServerInformation}");

                //if (!(this.ServerInformation.PSobject.Properties.name.Contains("SessionId")
                // && this.ServerInformation.PSobject.Properties.name.Contains("Version")
                // && this.ServerInformation.PSobject.Properties.name.Contains("ViewOnly")

                // && this.ServerInformation.PSobject.Properties.name.Contains("MachineName")
                // && this.ServerInformation.PSobject.Properties.name.Contains("Username")
                // && this.ServerInformation.PSobject.Properties.name.Contains("WindowsVersion")
                // && this.ServerInformation.PSobject.Properties.name.Contains("Screens"))
                // )


                //{
                //    clientFunc.Showlog("Invalid server information object.");
                //}

                var selectedScreen = this.ServerInformation.Screens;

                var localScreen = new myScreen();

                this.ViewerConfiguration = new clientViewerConfiguration();

                this.ViewerConfiguration.RemoteDesktopWidth = selectedScreen.Width;
                this.ViewerConfiguration.RemoteDesktopHeight = selectedScreen.Height;
                /*
                # If remote screen is bigger than local screen, we will resize remote screen to fit 90% of local screen.
                # Supports screen orientation (Horizontal / Vertical)
                */
                if (localScreen.Width <= selectedScreen.Width || localScreen.Height <= selectedScreen.Height)
                {
                    int adjustRatio = 80;

                    bool adjustVertically = localScreen.Width > localScreen.Height;

                    if (adjustVertically)
                    {
                        this.ViewerConfiguration.VirtualDesktopWidth = localScreen.Width * adjustRatio / 100;

                        int remoteResizedRatio = this.ViewerConfiguration.VirtualDesktopWidth * 100 / selectedScreen.Width;

                        this.ViewerConfiguration.VirtualDesktopHeight = selectedScreen.Height * remoteResizedRatio / 100;
                    }
                    else
                    {
                        this.ViewerConfiguration.VirtualDesktopHeight = localScreen.Height * adjustRatio/ 100;

                        int remoteResizedRatio = this.ViewerConfiguration.VirtualDesktopHeight * 100 / selectedScreen.Height;

                        this.ViewerConfiguration.VirtualDesktopWidth = selectedScreen.Width * remoteResizedRatio/ 100;
                    }
                }
                else
                {
                    this.ViewerConfiguration.VirtualDesktopWidth = selectedScreen.Width;
                    this.ViewerConfiguration.VirtualDesktopHeight = selectedScreen.Height;
                }

                /*
                 * If remote desktop resize is forced, we apply defined ratio to current configuration 
                 */
                if (this.ResizeRatio >= 30 && this.ResizeRatio <= 99)
                {
                    this.ViewerConfiguration.VirtualDesktopWidth = (selectedScreen.Width * this.ResizeRatio) / 100;
                    this.ViewerConfiguration.VirtualDesktopHeight = (selectedScreen.Height * this.ResizeRatio) / 100;
                }

                this.ViewerConfiguration.RequireResize = this.ViewerConfiguration.VirtualDesktopWidth != selectedScreen.Width
                                                          || this.ViewerConfiguration.VirtualDesktopHeight != selectedScreen.Height;

                this.ViewerConfiguration.ScreenX_Delta = selectedScreen.X;
                this.ViewerConfiguration.ScreenY_Delta = selectedScreen.Y;

                if (this.ViewerConfiguration.RequireResize)
                {
                    this.ViewerConfiguration.ScreenX_Ratio = (float)selectedScreen.Width / this.ViewerConfiguration.VirtualDesktopWidth;
                    this.ViewerConfiguration.ScreenY_Ratio = (float)selectedScreen.Height / this.ViewerConfiguration.VirtualDesktopHeight;
                }

                var viewerExpectation = new PSCustomObject
                {
                    ScreenName = selectedScreen.Name,
                    ImageCompressionQuality = this.ImageCompressionQuality,
                    PacketSize = this.PacketSize,
                    BlockSize = this.BlockSize,
                    LogonUI = this.LogonUI,
                };

                client.WriteJson(viewerExpectation);

                switch (Enum.Parse(typeof(ProtocolCommand), client.ReadLine(5 * 1000)))
                {
                    case ProtocolCommand.Success:
                        break;
                    case ProtocolCommand.Fail:
                        break;
                    case ProtocolCommand.RequestSession:
                        break;
                    case ProtocolCommand.AttachToSession:
                        break;
                    case ProtocolCommand.BadRequest:
                        break;
                    case ProtocolCommand.ResourceFound:
                        break;
                    case ProtocolCommand.ResourceNotFound:
                        break;
                    case ProtocolCommand.LogonUIAccessDenied:
                        clientFunc.Showlog("Could not access LogonUI / Winlogon desktop." +
                          " \r\n To access LogonUI desktop, you must have NT AUTHORITY/System privilege in current active session.");

                        break;
                    case ProtocolCommand.LogonUIWrongSession:

                        clientFunc.Showlog("Could not access LogonUI / Winlogon desktop." +
                          "\r\n To access LogonUI desktop, server process must be running under active Windows Session.");

                        break;
                    default:

                        clientFunc.Showlog("Remote server did not acknoledged our expectation in time.");

                        break;
                }


            }
            catch (Exception err)
            {
                clientFunc.Showlog(err.Message);
                this.CloseSession();
            }
            finally {
                if (!(client is null))
                {
                    client.Close();
                }
            }
        }

        public void CloseSession()
        {
            if (this.ClientDesktop != null)
            {
                this.ClientDesktop.Close();
            }

            if (this.ClientEvents != null)
            {
                this.ClientEvents.Close();
            }

            this.ClientDesktop = null;
            this.ClientEvents = null;

            this.ServerInformation = null;
            this.ViewerConfiguration = null;


            clientFunc.Showlog("CloseSession");
        }


        public bool HasSession()
        {
            return this.ServerInformation != null && this.ViewerConfiguration != null;
        }

        public void CheckSession()
        {
            if (this.HasSession())
            {
                clientFunc.Showlog("Session is Missing...");
                //throw Exception;
            }
        }

        public ClientIO ConnectWorker(WorkerKind workerKind)
        {
            this.CheckSession();
            var client = new ClientIO(Server.ToString(), ServerPort, UseTLSv1_3);

            try
            {
                client.Connect();
                client.Authentify(this.SecurePassword);
                client.WriteString(ProtocolCommand.AttachToSession.ToString());
                client.WriteString(this.ServerInformation.SessionId);

                string connectRes = client.ReadLine(5 * 1000);

                switch (Enum.Parse(typeof(ProtocolCommand), connectRes))
                {
                    case ProtocolCommand.ResourceFound:
                        client.WriteString(workerKind.ToString());

                        break;

                    case ProtocolCommand.ResourceNotFound:
                        throw new Exception();

                        break;

                    default:
                        throw new Exception();

                        break;
                }

                return client;
            }
            catch (Exception e)
            {
                clientFunc.Showlog(e.Message);
                if (client != null)
                    client.Close();
                //throw new Exception();
                return client;
            }

        }

        public void ConnectDesktopWorker()
        {
            this.ClientDesktop = this.ConnectWorker(WorkerKind.Desktop);
        }

        public void ConnectEventsWorker()
        {
            this.ClientEvents = this.ConnectWorker(WorkerKind.Events);
        }


    }
}
